/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/
#include "stdafx.h"

#include "otlkcon_sqlite_utils.h"

extern CRITICAL_SECTION otlkcon_sqlite_cs;
int transactThread = 0;

extern sqlite3 *sqlObjHandle;

void XTraceFunc(void *arg1, const char *arg2)
{
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
                    L"IProp::xTraceFunc: '%S'\n",arg2);
}

otlkcon_sqlite_helper::otlkcon_sqlite_helper( char *df, otlkcon_memory *m )
{
    name = NULL;
    memObj = new otlkcon_memory( m );
    transactions = 0;
    threadId = GetCurrentThreadId();

    databaseFilename = memObj->_strdup( df );
    if( sqlObjHandle )
    {
        databaseHandle = sqlObjHandle;
        goto sqlitehelper_end;
    }

    {
		OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "otlkcon_sqlite_helper");

		databaseHandle = NULL;

#ifdef OTLKCON_DELETE_MSTORE_DB
        // For debugging, start with a fresh file
        DeleteFile(databaseFilename);
        Sleep( OTLKCON_DELETE_PAUSE );
#endif // OTLKCON_DELETE_MSTORE_DB

		int sqlRes = sqlite3_open(databaseFilename, &databaseHandle);
		if (  sqlRes != SQLITE_OK )
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"otlkcon_sqlite_helper sqlite3_open error: %S", 
				sqlite3_errmsg(databaseHandle));
			OTLKCON_DEBUGBREAK
		}
		sqlRes = sqlite3_busy_timeout(databaseHandle, 20000);

#ifdef OTLKCON_SQLITE_TRACE
	//	sqlite3_trace(databaseHandle, XTraceFunc, NULL);
#endif

        // If we are debugging attach the stand-in database
        // file for memory debugging.  If we are not debugging
        // attach the real memory database
#ifdef OTLKCON_DEBUG_MEM_DB
        sqlRes = sqlCommand("ATTACH DATABASE '%s' AS memDb",
                                OTLKCON_DEBUG_MEM_DB_FILENAME);
#else
		sqlRes = sqlCommand("ATTACH DATABASE ':memory:' AS memDb");
#endif //OTLKCON_DEBUG_MEM_DB

		sqlObjHandle = databaseHandle;

		OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "otlkcon_sqlite_helper");
	}

sqlitehelper_end:
   ;
}

otlkcon_sqlite_helper::~otlkcon_sqlite_helper()
{
	if(NULL != databaseHandle)
	{
		sqlite3_close(databaseHandle);
		databaseHandle = NULL;
		
		if(NULL != databaseFilename)
		{
			memObj->free(databaseFilename);
			databaseFilename = NULL;
		} // if
		if(NULL != name)
		{
			memObj->free(name);
			name = NULL;
		} // if
		delete memObj;
		memObj = NULL;
	} // if
}

HRESULT otlkcon_sqlite_helper::init(const char *nm, char *dbf)
{
    HRESULT result = S_OK;

    if(NULL != name)
    {
		memObj->free(name);
    } // if
	name             = memObj->_strdup( nm );
	
	if(NULL != databaseFilename)
	{
		memObj->free(databaseFilename);
	} // if
    databaseFilename = memObj->_strdup( dbf );

    return result;
}

HRESULT otlkcon_sqlite_helper::sqlCmdBegin()
{
    HRESULT result = S_OK;
    
    		//OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
              //  L"}}}}Begin %d\n", transactions+1 );

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdBegin");

    if( transactions < 0 )
    {
        // Error
        OTLKCON_DEBUGBREAK;
    }

    if( transactions++ == 0 )
    {
     //   if( transactThread )
     //   {
     //       OTLKCON_DEBUGBREAK;
     //   }

        result = sqlCommand("BEGIN EXCLUSIVE");
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK;
			result = E_FAIL;
        }      

        transactThread = GetCurrentThreadId();
    }

    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdBegin");

    return result;
}

HRESULT otlkcon_sqlite_helper::sqlCmdCommit()
{
    HRESULT result = S_OK;

    		//OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
              //  L"}}}}Commit %d\n", transactions );

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdCommit");

    if( transactions < 1 )
    {
        // Error
        OTLKCON_DEBUGBREAK;
    }   

    if( transactions-- == 1 )
    {
        result = sqlCommand("COMMIT");
		if( result != S_OK )
		{
			OTLKCON_DEBUGBREAK;
			result = E_FAIL;
		}      

        transactThread = 0;
    }

    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdCommit");

    return result;
}

HRESULT otlkcon_sqlite_helper::sqlCmdRollback()
{
    HRESULT result = S_OK;

    //		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
      //          L"}}}}Rollback\n" );

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdRollback");

    if( transactions < 1 )
    {
        // Error
        OTLKCON_DEBUGBREAK;
    }

    result = sqlCommand("ROLLBACK");

    transactions = 0;
    transactThread = 0;

    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "sqlCmdRollback");

    return result;
}

HRESULT otlkcon_sqlite_helper::sqlCmdSingleInt(int *res, ULONG colNum, const char * format, ... )
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt = NULL;
    char * queryStr = NULL;
    const char **queryRemainder = NULL;
    int sqlRes = S_OK;
    va_list argList = NULL;
    int querySize = 0;

    va_start(argList, format);
    querySize = _vscprintf(format, argList);
    va_end(argList);

    if( querySize > OTLKCON_QUERY_MAX_LEN + 1 )
    {
        OTLKCON_DEBUGBREAK
        return E_OUTOFMEMORY;
    } 

    queryStr = (char *)memObj->malloc( querySize+1 );

    va_start(argList, format);
    result = _vsnprintf_s( queryStr, 
                querySize+1, OTLKCON_QUERY_MAX_LEN, format, argList);
    va_end(argList);

    result = S_OK;

    do
    {
//		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
//			L"SQL COMMAND: %S\n",queryStr);
        sqlRes = sqlite3_prepare( databaseHandle, queryStr, -1, &queryStmt, queryRemainder);
		if(SQLITE_OK != sqlRes)
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"%S sqlite3_prepare error: %S\n\tqueryStr ='%S'\n",
				name, sqlite3_errmsg(databaseHandle), queryStr);
			break;
		} // if
        sqlRes = sqlite3_step(queryStmt);
        switch( sqlRes )
        {
            case SQLITE_DONE:
                result = MAPI_E_NOT_FOUND;
                goto sqlcmdsingleint_end;
                break;

            case SQLITE_ROW:
                *res = sqlite3_column_int(queryStmt, colNum);
                break;

            default:
                // Some sought of error
                break;
        }

        sqlRes = sqlite3_finalize(queryStmt);
    }
    while ( sqlRes == SQLITE_SCHEMA );

    if ( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"%S sqlite3_finalize error: %S\n\tqueryStr ='%S'\n", 
            name, sqlite3_errmsg(databaseHandle), queryStr);
        result = E_FAIL;
        OTLKCON_DEBUGBREAK
        goto sqlcmdsingleint_end;
    }

sqlcmdsingleint_end:
    memObj->free(queryStr);
    return result;
}

HRESULT otlkcon_sqlite_helper::sqlCommand(const char * format, ... )
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt;
    char * queryStr;
    const char **queryRemainder = NULL;
    int sqlRes;
    va_list argList;
    int querySize = 0;

    va_start(argList, format);
    querySize = _vscprintf(format, argList);
    va_end(argList);

    if( querySize > OTLKCON_QUERY_MAX_LEN + 1 )
    {
        OTLKCON_DEBUGBREAK
        return E_OUTOFMEMORY;
    } 

    queryStr = (char *)memObj->malloc( querySize+1 );

    va_start(argList, format);
    result = _vsnprintf_s( queryStr, querySize+1, 
                    OTLKCON_QUERY_MAX_LEN, format, argList);
    va_end(argList);

//	OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL,
//		L"SQL COMMAND: %S\n",queryStr);

    sqlRes = sqlite3_prepare( databaseHandle, queryStr, -1, &queryStmt, queryRemainder);
	if(SQLITE_OK != sqlRes)
	{
		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			L"%S sqlite3_prepare error: %S\n\tqueryStr ='%S'\n",
			name, sqlite3_errmsg(databaseHandle), queryStr);
		goto sqlcommand_end;
	} // if
    sqlRes = sqlite3_step(queryStmt);
    switch( sqlRes )
    {
        case SQLITE_DONE:
            // Expected condition
            break;

        case SQLITE_ROW:
            // We don't do anything with rows returned
            break;

        default:
            // Some sought of error
            break;
    }
    sqlRes = sqlite3_finalize(queryStmt);
    if( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"%S sqlite3_finalize error: %S\n\tqueryStr ='%S'\n",
            name, sqlite3_errmsg(databaseHandle), queryStr);
        OTLKCON_DEBUGBREAK
        goto sqlcommand_end;
    }

sqlcommand_end:
    
    memObj->free(queryStr);
    return sqlRes;
}

HRESULT otlkcon_sqlite_helper::buildQuery(char ** dest, const char * format, ... )
{
    HRESULT result = S_OK;
    va_list argList;
    int querySize = 0;

    va_start(argList, format);
    querySize = _vscprintf(format, argList);
    va_end(argList);

    if( querySize > OTLKCON_QUERY_MAX_LEN + 1 )
    {
        return E_OUTOFMEMORY;
    } 

    *dest = (char *)memObj->malloc( querySize+1 );

    va_start(argList, format);
    result = _vsnprintf_s( *dest, querySize+1,
                    OTLKCON_QUERY_MAX_LEN, format, argList);
    va_end(argList);

    return (result < 0) ? E_FAIL: S_OK;
}

HRESULT otlkcon_sqlite_helper::lastId( __int64 *id )
{
    HRESULT result = S_OK;

    *id = sqlite3_last_insert_rowid(databaseHandle);

    return result;
}
